
#include "gh.h"

static const char *nVertexProgramSrc =
    "!!NVvp4.0"
    NL "FLOAT PARAM RotZ[4] = { program.local[0..3] };"
    NL
    NL "main:"
    NL "    DP4.F result.position.x, RotZ[0], vertex.attrib[0];"
    NL "    DP4.F result.position.y, RotZ[1], vertex.attrib[0];"
    NL "    DP4.F result.position.z, RotZ[2], vertex.attrib[0];"
    NL "    DP4.F result.position.w, RotZ[3], vertex.attrib[0];"
    NL "    MOV.F result.attrib[0], vertex.attrib[1];"
    NL "    RET;"
    NL "END";

static const char *nFragmentProgramSrc =
    "!!NVfp4.0"
    NL
    NL "main:"
    NL "    MOV.F result.color, fragment.attrib[0];"
    NL "    RET;"
    NL "END";

static GLuint nProgram[2] = { 0 };
static GLuint nVBO = 0;
static GLuint64 nVBOAddress;

#define VERTEX_SIZE (5 * sizeof(float))

int ghAppBegin(void) {
    if (!GLEW_NV_gpu_program4) {
        GH_ERROR_SET("Program requires GL_NV_gpu_program4 extension.");
        return -1;
    }

    /* create programs */
    glGenProgramsARB(2, nProgram);

    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    if (ghNVProgramSetString(GL_VERTEX_PROGRAM_ARB, nVertexProgramSrc) < 0) {
        return -1;
    }

    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);
    if (ghNVProgramSetString(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgramSrc) < 0) {
        return -1;
    }

    /* create vbo */ {
        float vertices[] = {
            -0.7f, -0.7f, 1.0f, 0.0f, 0.0f,
            0.7f, -0.7f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.7f, 0.0f, 0.0f, 1.0f
        };

        glGenBuffers(1, &nVBO);
        glBindBuffer(GL_ARRAY_BUFFER, nVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glGetBufferParameterui64vNV(GL_ARRAY_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &nVBOAddress);
        glMakeBufferResidentNV(GL_ARRAY_BUFFER, GL_READ_ONLY);
    }

    /* set GL state */
    glEnable(GL_VERTEX_PROGRAM_ARB);
    glEnable(GL_FRAGMENT_PROGRAM_ARB);

    return 0;
}

int ghAppFrame(double dt, double time) {
    /* compute rotation matrix */
    float sinv = sinf((float) time);
    float cosv = cosf((float) time);
    float rotZ[16] = {
        cosv, -sinv, 0.0f, 0.0f,
        sinv, cosv, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f
    };

    /* clear buffers */
    glClear(GL_COLOR_BUFFER_BIT);

    /* set OpenGL programs */
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);

    /* set rotation matrix */
    glProgramLocalParameters4fvEXT(GL_VERTEX_PROGRAM_ARB, 0, 4, rotZ);

    /* draw */
    glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribFormatNV(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float));
    glVertexAttribFormatNV(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float));
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, nVBOAddress, 3 * VERTEX_SIZE);
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 1, nVBOAddress + sizeof(float) * 2, 3 * VERTEX_SIZE - sizeof(float) * 2);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    return 0;
}

int ghAppEnd(void) {
    glDeleteProgramsARB(2, nProgram);
    glDeleteBuffers(1, &nVBO);

    return 0;
}

int ghAppMouseCallback(int x, int y, int action) {
    return 0;
}
